package ar.edu.itba.pod.legajo45129.manager;

import java.rmi.AccessException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.List;

import org.apache.log4j.Logger;
import org.joda.time.Duration;

import ar.edu.itba.balance.api.AgentsBalancer;
import ar.edu.itba.balance.api.AgentsTransfer;
import ar.edu.itba.balance.api.NodeAgent;
import ar.edu.itba.balance.api.NotCoordinatorException;
import ar.edu.itba.event.RemoteEventDispatcher;
import ar.edu.itba.node.Node;
import ar.edu.itba.node.NodeInformation;
import ar.edu.itba.node.api.ClusterAdministration;
import ar.edu.itba.node.api.StatisticReports;
import ar.edu.itba.pod.agent.runner.Simulation;
import ar.edu.itba.pod.legajo45129.agentSimulation.DistributedSimulation;
import ar.edu.itba.pod.legajo45129.balance.api.AgentsBalancerImpl;
import ar.edu.itba.pod.legajo45129.balance.api.AgentsTransferImpl;
import ar.edu.itba.pod.legajo45129.balance.api.RemoteStatistics;
import ar.edu.itba.pod.legajo45129.event.RemoteEventDispatcherImpl;
import ar.edu.itba.pod.legajo45129.node.api.ClusterAdministrationImpl;

import com.google.common.base.Preconditions;

public class NodeManager implements Node {
	private static NodeManager nodeManager;
	private NodeInformation nodeInformation;
	private ClusterAdministration clusterAdministration;
	private AgentsBalancer agentsBalancer;
	private NodeInformation coordinator;
	private RemoteEventDispatcherImpl remoteEventDispatcher;
	private Simulation simulation;
	private AgentsTransfer agentsTransfer;
	private StatisticReports stadistics;
	
	private static Logger logger = Logger.getLogger(NodeManager.class);

	public static NodeManager getInstance() {
		if (nodeManager == null) {
			nodeManager = new NodeManager();
		}
		return nodeManager;
	}

	public void configure(String host, int port, String id,
			Simulation simulation) {
		this.nodeInformation = new NodeInformation(host, port, id);
		Preconditions.checkNotNull(simulation);
		this.simulation = simulation;
		try {
			this.clusterAdministration = new ClusterAdministrationImpl();
			this.agentsBalancer = new AgentsBalancerImpl();
			DistributedSimulation ds = (DistributedSimulation) simulation;
			this.remoteEventDispatcher = new RemoteEventDispatcherImpl(
					ds.dispatcher());
			this.agentsTransfer = new AgentsTransferImpl();
			this.stadistics = new RemoteStatistics(this.simulation);
			simulation.start(Duration.standardMinutes(10));
		} catch (RemoteException e) {
			throw new IllegalArgumentException(e.getLocalizedMessage());
		}
	}

	public ClusterAdministration getClusterAdministration(String host, int port)
			throws RemoteException, NotBoundException {
		Registry registry = LocateRegistry.getRegistry(host, port);
		return (ClusterAdministration) registry.lookup(CLUSTER_COMUNICATION);
	}

	public ClusterAdministration getClusterAdministration()
			throws RemoteException {
		return this.clusterAdministration;
	}

	public StatisticReports getStatisticReports() {
		return this.stadistics;
	}

	public StatisticReports getStatisticReports(String host, int port) throws RemoteException, NotBoundException {
		Registry registry;
		StatisticReports s;
		registry = LocateRegistry.getRegistry(host, port);
		s = (StatisticReports) registry.lookup(STATISTIC_REPORTS);
		return s;
	}


	public Simulation getSimulation() {
		Preconditions.checkNotNull(this.simulation);
		return this.simulation;
	}

	public NodeInformation getNodeInformation() {
		Preconditions.checkNotNull(this.nodeInformation);
		return this.nodeInformation;
	}

	public AgentsBalancer getAgentsBalancer(String host, int port)
			throws RemoteException, NotBoundException {
		Registry registry = LocateRegistry.getRegistry(host, port);
		AgentsBalancer ab;
		// try {
		ab = (AgentsBalancer) registry.lookup(AGENTS_BALANCER);
		return ab;
	}

	public AgentsBalancer getAgentsBalancer() throws RemoteException {
		if (this.agentsBalancer == null) {
			throw new IllegalAccessError("call configure method first.");
		}
		return this.agentsBalancer;
	}

	/**
	 * Compare local node id with given id
	 * 
	 * @param id
	 * @return true if local node id is less than given id.
	 */
	public boolean isGreaterThan(String id) {
		// long remoteId = Long.parseLong(id);
		// long localId = Long.parseLong(this.nodeInformation.id());
		//
		//
		// return localId >= remoteId;
		Preconditions.checkNotNull(this.nodeInformation);
		Preconditions.checkNotNull(this.nodeInformation.id());
		return this.nodeInformation.id().compareTo(id) >= 0;
	}

	public NodeInformation getCoordinator() {
		return this.coordinator;
	}

	public void setCoordinator(NodeInformation coordinator) {
		this.coordinator = coordinator;
	}

	public void setNodeInformation(NodeInformation ni) {
		this.nodeInformation = ni;

	}

	public RemoteEventDispatcher getRemoteEventDispatcher(String host, int port)
			throws RemoteException, NotBoundException {
		Registry registry = LocateRegistry.getRegistry(host, port);
		return (RemoteEventDispatcher) registry
				.lookup(DISTRIBUTED_EVENT_DISPATCHER);
	}

	public RemoteEventDispatcher getRemoteEventDispatcher() {
		return this.remoteEventDispatcher;
	}

	public AgentsTransfer getAgentsTransfer() {
		return this.agentsTransfer;
	}

	public AgentsTransfer getAgentsTransfer(String host, int port)
			throws RemoteException, NotBoundException {
		Registry registry = LocateRegistry.getRegistry(host, port);
		return (AgentsTransfer) registry.lookup(AGENTS_TRANSFER);
	}

	public void shutdown() {
		int agentsNum = this.simulation.agentsRunning();
		List<NodeAgent> agentsOnThisNode = null;
		try {
			agentsOnThisNode = getInstance().getAgentsTransfer().stopAndGet(
					agentsNum);
			// getInstance().getClusterAdministration().disconnectFromGroup(getInstance().getNodeInformation());
			getInstance().getAgentsBalancer(
					NodeManager.getInstance().getCoordinator().host(),
					NodeManager.getInstance().getCoordinator().port())
					.shutdown(agentsOnThisNode);
		} catch (RemoteException e) {
			logger.warn(e);
		} catch (NotCoordinatorException e) {
			NodeInformation newCoordinator = e.getNewCoordinator();
			try {
				getInstance().getAgentsBalancer(newCoordinator.host(),
						newCoordinator.port()).shutdown(agentsOnThisNode);
			} catch (RemoteException e1) {
				logger.warn(e);
			} catch (NotCoordinatorException e1) {
				logger.warn(e);
			} catch (NotBoundException e1) {
				logger.warn(e);
			}
		} catch (NotBoundException e) {
			logger.warn(e);
		}
	}

	public void unbind() {
		int port = getInstance().nodeInformation.port();
		try {
			LocateRegistry.getRegistry(port).unbind(CLUSTER_COMUNICATION);
			LocateRegistry.getRegistry(port).unbind(DISTRIBUTED_EVENT_DISPATCHER);
			LocateRegistry.getRegistry(port).unbind(AGENTS_BALANCER);
			LocateRegistry.getRegistry(port).unbind(AGENTS_TRANSFER);
			LocateRegistry.getRegistry(port).unbind(STATISTIC_REPORTS);
		} catch (AccessException e) {
			// do nothing
			logger.warn(e);
		} catch (RemoteException e) {
			// do nothing
			logger.warn(e);
		} catch (NotBoundException e) {
			// do nothing
			logger.warn(e);
		}


	}
}
